﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace DisplayDemo
{
    public interface ISubscription<T> : IDisposable
    {
        ValueTask<T> Read();
    }

    internal class Subscription<T>(MessageQueue<T> queue, int size) : ISubscription<T>
    {
        int disposed;

        private readonly MessageQueue<T> queue = queue;
        private readonly Channel<T> chan = Channel.CreateBounded<T>(new BoundedChannelOptions(size)
        {
            FullMode = BoundedChannelFullMode.DropOldest
        });

        public async ValueTask<T> Read()
        {
            return await chan.Reader.ReadAsync();
        }

        public bool TryWrite(T data)
        {
            return chan.Writer.TryWrite(data);
        }

        internal void Complete(Exception? ex)
        {
            if (Interlocked.CompareExchange(ref disposed, 1, 0) == 0)
            {
                chan.Writer.TryComplete(ex);
            }
        }

        public void Dispose()
        {
            if (Interlocked.CompareExchange(ref disposed, 1, 0) == 0)
            {
                queue.Unsubscribe(this);
                chan.Writer.TryComplete();
            }
        }
    }

    public class MessageQueue<T> : IDisposable
    {
        private readonly object lockObj = new();
        private volatile List<Subscription<T>> subscriptions = [];

        private bool disposed;

        public void Send(T v)
        {
            foreach (var sub in subscriptions)
            {
                sub.TryWrite(v);
            }
        }

        public ISubscription<T> Subscribe(int size = 1)
        {
            lock (lockObj)
            {
                ObjectDisposedException.ThrowIf(disposed, this);

                var sub = new Subscription<T>(this, size);
                var copy = new List<Subscription<T>>(subscriptions)
                {
                    sub
                };
                subscriptions = copy;
                return sub;
            }
        }

        public void UnsubscribeAll(Exception? ex)
        {
            lock (lockObj)
            {
                if (disposed)
                {
                    return;
                }

                foreach (var sub in subscriptions)
                {
                    sub.Complete(ex);
                }
                subscriptions = [];
            }
        }

        internal void Unsubscribe(Subscription<T> sub)
        {
            lock (lockObj)
            {
                if (disposed)
                {
                    return;
                }

                var copy = new List<Subscription<T>>(subscriptions);
                copy.Remove(sub);
                subscriptions = copy;
            }
        }

        public void Dispose()
        {
            lock (lockObj)
            {
                if (disposed)
                {
                    return;
                }

                foreach (var sub in subscriptions)
                {
                    sub.Dispose();
                }
                disposed = true;
            }
        }
    }
}
